import * as React from 'react';
import {HTMLAttributes, TouchEventHandler,UIEventHandler,MouseEventHandler, useEffect, useRef, useState} from 'react';
import './index.scss';
import scrollbarWidth from './scrollbar-width';

type Props = HTMLAttributes<HTMLDivElement> & {
  initialThumbTop?: number;
  hideOnPhone?: boolean;
  autoHide?: boolean;
  maxPullingHeight?: number;
  onPull?: () => void;
  onReachBottom?: () => void;
}

const isTouchDevice = 'ontouchstart' in document.documentElement;

const Scroll: React.FunctionComponent<Props> = (props) => {
  const {children, initialThumbTop = 0, hideOnPhone = true, autoHide = false, maxPullingHeight = 150, onPull, onReachBottom, ...rest} = props;
  const [thumbHeight, setThumbHeight] = useState(0);
  const [thumbTop, _setThumbTop] = useState(initialThumbTop);
  const [barVisible, setBarVisible] = useState(autoHide ? false : true);
  const containerRef = useRef<HTMLDivElement>(null);

  const setThumbTop = (number: number,viewHeight?: number,scrollHeight?: number) => {
    viewHeight = viewHeight || containerRef.current!.getBoundingClientRect().height;
    scrollHeight = scrollHeight || containerRef.current!.scrollHeight;
    if (number < 0) return;
    const maxThumbTop = (scrollHeight - viewHeight) * viewHeight / scrollHeight;
    if (number > maxThumbTop) return;
    _setThumbTop(number);
  };

  const x = () => {
    const scrollHeight = containerRef.current!.scrollHeight;
    const viewHeight = containerRef.current!.getBoundingClientRect().height;
    const scrollTop = containerRef.current!.scrollTop;
    return {scrollHeight, viewHeight, scrollTop};
  };

  const calcThumbHeightAndTop = (scrollHeight: number,viewHeight: number,scrollTop: number) => {
    setThumbHeight(viewHeight * viewHeight / scrollHeight); // 详见doc/thumb高度计算.png
    setThumbTop(scrollTop * viewHeight / scrollHeight,viewHeight,scrollHeight);
  };

  const timerIdRef = useRef<number | null>(null);

  const checkReachBottom = (scrollHeight: number,viewHeight: number,scrollTop: number) => {
    if (scrollTop + viewHeight === scrollHeight) onReachBottom && onReachBottom();
  };

  // 滚动
  const onScroll: UIEventHandler = (e) => {
    if (autoHide && (!isTouchDevice || !hideOnPhone)) {
      setBarVisible(true);

      if (timerIdRef.current !== null) {
        window.clearTimeout(timerIdRef.current);
      }
      timerIdRef.current = window.setTimeout(() => {
        setBarVisible(false);
      }, 300);
    }

    const {scrollHeight, viewHeight, scrollTop} = x();
    calcThumbHeightAndTop(scrollHeight, viewHeight, scrollTop);
    checkReachBottom(scrollHeight, viewHeight, scrollTop);
  };

  // 拖动
  const draggingRef = useRef(false);
  const startDragYRef = useRef(0);
  const lastDragYRef = useRef(0);
  const onMouseDownThumb: MouseEventHandler = (e) => {
    e.preventDefault(); // 防止拖动滑块时选中文字
    draggingRef.current = true;
    startDragYRef.current = e.clientY;
    lastDragYRef.current = thumbTop;
  };
  const onMouseMoveThumb = (e: MouseEvent) => {
    if (draggingRef.current) {
      const delta = e.clientY - startDragYRef.current; // 以第一次移动的move的距离为增量
      const newThumbTop = lastDragYRef.current + delta;
      setThumbTop(newThumbTop); // ←异步的
      const {scrollHeight, viewHeight} = x();
      containerRef.current!.scrollTop = newThumbTop * scrollHeight / viewHeight; // ←立即生效
    }
  };
  const onMouseUpThumb = ()=>{
    draggingRef.current = false;
  };

  useEffect(() => {
    // mounted时 对thumb进行初始化
    const {scrollHeight, viewHeight, scrollTop} = x();
    calcThumbHeightAndTop(scrollHeight,viewHeight,scrollTop);

    // 监听thumb拖动事件
    document.addEventListener('mouseup', onMouseUpThumb);
    document.addEventListener('mousemove', onMouseMoveThumb);

    return () => {
      document.removeEventListener('mouseup', onMouseUpThumb);
      document.removeEventListener('mousemove', onMouseMoveThumb);
    };
  }, []);

  // 下拉刷新
  const [translateY, _setTranslateY] = useState(0);
  const setTranslateY = (y: number) => {
    if (y < 0) return _setTranslateY(0);
    if(y > maxPullingHeight) return _setTranslateY(maxPullingHeight);
    _setTranslateY(y);
  };
  const lastTouchRef = useRef(0);
  const moveCount = useRef(0);
  const pulling = useRef(false);
  const onTouchStart: TouchEventHandler = (e) => {
    const scrollTop = containerRef.current!.scrollTop;
    if(scrollTop !== 0) return; // 只有scrollTop为0时才会执行下拉刷新逻辑
    pulling.current = true;
    lastTouchRef.current = e.touches[0].clientY;
    moveCount.current = 0;
  };
  const onTouchMove: TouchEventHandler = (e) => {
    const currentY = e.touches[0].clientY;
    const deltaY = currentY - lastTouchRef.current;
    moveCount.current += 1;

    // 一开始时是向下翻,后续的move都不会执行下拉刷新 通过是否将变量pulling保持true来判断一开始时是向上翻还是向下翻
    if (moveCount.current === 1 && deltaY < 0) return pulling.current = false;

    // 一旦你一开始时是向下翻,那么后续的TouchMove都不会执行下拉刷新
    if (pulling.current === false) return;

    setTranslateY(translateY + deltaY);

    lastTouchRef.current = currentY;
  };
  const onTouchEnd: TouchEventHandler = () => {
    if(pulling.current === true && translateY!==0) {
      setTranslateY(0);
      onPull && onPull();
      pulling.current = false;
    }
  };

  return (
    <div className="x-scroll" {...rest}>
      <div className="x-scroll-inner" style={{right: -scrollbarWidth(),transform:`translateY(${translateY}px)`}}
        ref={containerRef}
        onScroll={onScroll}
        onTouchMove={onTouchMove}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
      >
        {children}
      </div>

      {
        (hideOnPhone && isTouchDevice/*因为hideOnPhone是默认值,故还需要一个值来确保pc端不会被隐藏*/) ? null/*触屏设备时不希望显示滚动条 默认*/ :
          barVisible /*默认为true, 只有autoHide为true时才可能为false*/ ? (
            <div className="x-scroll-scrollbar">
              <div className="x-scroll-scrollbar-track">
                <div className="x-scroll-scrollbar-track-piece"/>
                <div className="x-scroll-scrollbar-track-thumb"
                  style={{height: thumbHeight, transform: `translateY(${thumbTop}px)`}}
                  onMouseDown={onMouseDownThumb}
                />
              </div>
              <div className="x-scroll-scrollbar-button"/>
            </div>
          ) : null
      }

      <div className='x-scroll-pulling' style={{height:translateY}}>
        {translateY === maxPullingHeight?
          <span className="x-scroll-pulling-text">释放即可更新</span> :
          <span className='x-scroll-pulling-icon'>↓</span>}
      </div>
    </div>
  );
};

export default Scroll;
